﻿using System;
using Aeon.Emulator.Decoding;

namespace Aeon.Emulator.DebugSupport
{
    /// <summary>
    /// Decodes instruction operands into an easier to process structure.
    /// </summary>
    internal static class InstructionDecoder
    {
        #region Public Static Methods
        /// <summary>
        /// Returns a structure containing decoded instruction operands.
        /// </summary>
        /// <param name="opcode">Information about the supplied opcode.</param>
        /// <param name="rawData">Operands following the opcode.</param>
        /// <param name="prefixes">Prefixes in effect for the instruction.</param>
        /// <returns>Structure containing information about the decoded operands.</returns>
        public static DecodedOperands Decode(OpcodeInfo opcode, byte[] rawData, PrefixState prefixes)
        {
            int length;
            return Decode(opcode, rawData, prefixes, out length);
        }
        /// <summary>
        /// Returns the length of instruction operands in bytes.
        /// </summary>
        /// <param name="opcode">Information about the supplied opcode.</param>
        /// <param name="rawData">Operands following the opcode.</param>
        /// <param name="prefixes">Prefixes in effect for the instruction.</param>
        /// <returns>Length of instruction operands in bytes.</returns>
        public static int CalculateOperandLength(OpcodeInfo opcode, byte[] rawData, PrefixState prefixes)
        {
            int length;
            Decode(opcode, rawData, prefixes, out length);
            return length;
        }
        #endregion

        /// <summary>
        /// Returns a structure containing decoded instruction operands.
        /// </summary>
        /// <param name="opcode">Information about the supplied opcode.</param>
        /// <param name="rawData">Operands following the opcode.</param>
        /// <param name="prefixes">Prefixes in effect for the instruction.</param>
        /// <param name="length">Will be set to the length of the instruction operands in bytes.</param>
        /// <returns>Structure containing information about the decoded operands.</returns>
        private static DecodedOperands Decode(OpcodeInfo opcode, byte[] rawData, PrefixState prefixes, out int length)
        {
            var reader = new OperandReader(rawData);
            var operands = new DecodedOperands();

            length = 0;

            if(opcode.Operands.Count == 0)
                return operands;

            if(opcode.ModRmInfo != ModRmInfo.None)
            {
                byte modRmByte = reader.ReadByte();
                int mod = (modRmByte & 0xC0) >> 6;
                int rm = modRmByte & 0x07;

                for(int i = 0; i < opcode.Operands.Count; i++)
                {
                    var type = opcode.Operands[i];
                    if(type == OperandType.RegisterOrMemoryByte)
                        operands.SetOperand(i, DecodeRmbw(mod, rm, true, prefixes, ref reader));
                    else if(IsPointerOperand(type))
                    {
                        var operand = DecodeRmbw(mod, rm, false, prefixes, ref reader);
                        if(type == OperandType.EffectiveAddress)
                            operand.Type = CodeOperandType.EffectiveAddress;
                        else if(type == OperandType.RegisterOrMemoryWordNearPointer)
                            operand.Type = CodeOperandType.AbsoluteJumpAddress;
                        else if(type == OperandType.IndirectFarPointer)
                            operand.Type = CodeOperandType.IndirectFarMemoryAddress;
                        else if(type == OperandType.MemoryInt16 || type == OperandType.RegisterOrMemory16)
                            operand.OperandSize = CodeOperandSize.Word;
                        else if(type == OperandType.MemoryInt32 || type == OperandType.RegisterOrMemory32)
                            operand.OperandSize = CodeOperandSize.DoubleWord;
                        else if(type == OperandType.MemoryInt64)
                            operand.OperandSize = CodeOperandSize.QuadWord;
                        else if(type == OperandType.MemoryFloat32)
                            operand.OperandSize = CodeOperandSize.Single;
                        else if(type == OperandType.MemoryFloat64)
                            operand.OperandSize = CodeOperandSize.Double;
                        else if(type == OperandType.MemoryFloat80)
                            operand.OperandSize = CodeOperandSize.LongDouble;
                        
                        operands.SetOperand(i, operand);
                    }
                }

                if(opcode.ModRmInfo == ModRmInfo.All)
                {
                    int reg = (modRmByte & 0x38) >> 3;
                    for(int i = 0; i < opcode.Operands.Count; i++)
                    {
                        var type = opcode.Operands[i];
                        if(type == OperandType.RegisterByte)
                            operands.SetOperand(i, DecodeRb(reg));
                        else if(type == OperandType.RegisterWord)
                            operands.SetOperand(i, DecodeRw(reg, prefixes));
                        else if(type == OperandType.SegmentRegister)
                            operands.SetOperand(i, DecodeSreg(reg));
                    }
                }
            }

            for(int i = 0; i < opcode.Operands.Count; i++)
            {
                var type = opcode.Operands[i];
                if(IsKnownRegister(type))
                    operands.SetOperand(i, new CodeOperand(DecodeKnownRegister(type, (prefixes & PrefixState.OperandSize) != 0)));
                else if(type == OperandType.ImmediateByte)
                {
                    operands.SetOperand(i, new CodeOperand(CodeOperandType.Immediate, reader.ReadByte(), CodeOperandSize.Byte));
                }
                else if(type == OperandType.ImmediateByteExtend || type == OperandType.ImmediateRelativeByte)
                {
                    var operand = new CodeOperand(CodeOperandType.Immediate, (uint)(int)reader.ReadSByte(), GetOperandSize(false, prefixes));
                    if(type == OperandType.ImmediateRelativeByte)
                        operand.Type = CodeOperandType.RelativeJumpAddress;
                    
                    operands.SetOperand(i, operand);
                }
                else if(type == OperandType.ImmediateInt16)
                {
                    operands.SetOperand(i, new CodeOperand(CodeOperandType.Immediate, reader.ReadUInt16(), CodeOperandSize.Word));
                }
                else if(type == OperandType.ImmediateInt32)
                {
                    operands.SetOperand(i, new CodeOperand(CodeOperandType.Immediate, reader.ReadUInt32(), CodeOperandSize.DoubleWord));
                }
                else if(type == OperandType.ImmediateWord)
                {
                    uint value;
                    if((prefixes & PrefixState.OperandSize) == 0)
                        value = reader.ReadUInt16();
                    else
                        value = reader.ReadUInt32();

                    operands.SetOperand(i, new CodeOperand(CodeOperandType.Immediate, value, GetOperandSize(false, prefixes)));
                }
                else if(type == OperandType.ImmediateRelativeWord)
                {
                    uint value;
                    if((prefixes & PrefixState.OperandSize) == 0)
                        value = (uint)(int)reader.ReadInt16();
                    else
                        value = reader.ReadUInt32();

                    operands.SetOperand(i, new CodeOperand(CodeOperandType.RelativeJumpAddress, value, GetOperandSize(false, prefixes)));
                }
                else if(type == OperandType.MemoryOffsetByte || type == OperandType.MemoryOffsetWord)
                {
                    uint value;
                    if((prefixes & PrefixState.AddressSize) == 0)
                        value = (uint)(int)reader.ReadInt16();
                    else
                        value = reader.ReadUInt32();

                    operands.SetOperand(i, new CodeOperand(CodeMemoryBase.DisplacementOnly, value, GetOperandSize(type == OperandType.MemoryOffsetByte, prefixes)));
                }
                else if(type == OperandType.ImmediateFarPointer)
                {
                    uint value;
                    if((prefixes & PrefixState.AddressSize) == 0)
                        value = reader.ReadUInt16();
                    else
                        value = reader.ReadUInt32();

                    ushort segment = reader.ReadUInt16();
                    operands.SetOperand(i, CodeOperand.FarPointer(segment, value));
                }
            }

            length = reader.Position;
            return operands;
        }

        #region Private Static Methods
        /// <summary>
        /// Determines whether an operand is a normal pointer.
        /// </summary>
        /// <param name="type">The operand.</param>
        /// <returns>True if operand represents a normal pointer; otherwise false.</returns>
        private static bool IsPointerOperand(OperandType type)
        {
            return type == OperandType.RegisterOrMemoryWord || type == OperandType.EffectiveAddress
                || type == OperandType.IndirectFarPointer || type == OperandType.MemoryInt64
                || type == OperandType.RegisterOrMemory16 || type == OperandType.MemoryInt32
                || type == OperandType.MemoryFloat32 || type == OperandType.MemoryFloat64
                || type == OperandType.MemoryFloat80 || type == OperandType.RegisterOrMemoryWordNearPointer;
        }
        /// <summary>
        /// Returns a value indicating whether an operand is a known register.
        /// </summary>
        /// <param name="operand">Operand to test.</param>
        /// <returns>True if operand is a known register; otherwise false.</returns>
        private static bool IsKnownRegister(OperandType operand)
        {
            return operand == OperandType.RegisterAL || operand == OperandType.RegisterAH || operand == OperandType.RegisterAX ||
                operand == OperandType.RegisterBL || operand == OperandType.RegisterBH || operand == OperandType.RegisterBX ||
                operand == OperandType.RegisterCL || operand == OperandType.RegisterCH || operand == OperandType.RegisterCX ||
                operand == OperandType.RegisterDL || operand == OperandType.RegisterDH || operand == OperandType.RegisterDX ||
                operand == OperandType.RegisterSI || operand == OperandType.RegisterDI || operand == OperandType.RegisterSP ||
                operand == OperandType.RegisterBP || operand == OperandType.RegisterCS || operand == OperandType.RegisterSS ||
                operand == OperandType.RegisterDS || operand == OperandType.RegisterES || operand == OperandType.RegisterFS ||
                operand == OperandType.RegisterGS;
        }
        /// <summary>
        /// Returns a decoded register operand based on a supplied known register operand.
        /// </summary>
        /// <param name="o">Known register operand to decode.</param>
        /// <param name="size32">Value indicating whether the operand-size is 32-bit.</param>
        /// <returns>Decoded register operand based on the supplied known register operand.</returns>
        private static CodeRegister DecodeKnownRegister(OperandType o, bool size32)
        {
            switch(o)
            {
            case OperandType.RegisterAL: return CodeRegister.AL;
            case OperandType.RegisterAH: return CodeRegister.AH;
            case OperandType.RegisterAX: return size32 ? CodeRegister.EAX : CodeRegister.AX;
            case OperandType.RegisterBL: return CodeRegister.BL;
            case OperandType.RegisterBH: return CodeRegister.BH;
            case OperandType.RegisterBX: return size32 ? CodeRegister.EBX : CodeRegister.BX;
            case OperandType.RegisterCL: return CodeRegister.CL;
            case OperandType.RegisterCH: return CodeRegister.CH;
            case OperandType.RegisterCX: return size32 ? CodeRegister.ECX : CodeRegister.CX;
            case OperandType.RegisterDL: return CodeRegister.DL;
            case OperandType.RegisterDH: return CodeRegister.DH;
            case OperandType.RegisterDX: return size32 ? CodeRegister.EDX : CodeRegister.DX;
            case OperandType.RegisterSI: return size32 ? CodeRegister.ESI : CodeRegister.SI;
            case OperandType.RegisterDI: return size32 ? CodeRegister.EDI : CodeRegister.DI;
            case OperandType.RegisterSP: return size32 ? CodeRegister.ESP : CodeRegister.SP;
            case OperandType.RegisterBP: return size32 ? CodeRegister.EBP : CodeRegister.BP;
            case OperandType.RegisterCS: return CodeRegister.CS;
            case OperandType.RegisterSS: return CodeRegister.SS;
            case OperandType.RegisterDS: return CodeRegister.DS;
            case OperandType.RegisterES: return CodeRegister.ES;
            case OperandType.RegisterFS: return CodeRegister.FS;
            case OperandType.RegisterGS: return CodeRegister.GS;

            default:
                throw new ArgumentException();
            }
        }

        /// <summary>
        /// Returns a decoded register from an operand word register code.
        /// </summary>
        /// <param name="regCode">Operand word register code.</param>
        /// <param name="prefixes">Prefixes in effect for the instruction.</param>
        /// <returns>Decoded register from the operand word register code.</returns>
        private static CodeOperand DecodeRw(int regCode, PrefixState prefixes)
        {
            bool isWord = (prefixes & PrefixState.OperandSize) == 0;

            switch(regCode)
            {
            case 0: return isWord ? CodeRegister.AX : CodeRegister.EAX;
            case 1: return isWord ? CodeRegister.CX : CodeRegister.ECX;
            case 2: return isWord ? CodeRegister.DX : CodeRegister.EDX;
            case 3: return isWord ? CodeRegister.BX : CodeRegister.EBX;
            case 4: return isWord ? CodeRegister.SP : CodeRegister.ESP;
            case 5: return isWord ? CodeRegister.BP : CodeRegister.EBP;
            case 6: return isWord ? CodeRegister.SI : CodeRegister.ESI;
            case 7: return isWord ? CodeRegister.DI : CodeRegister.EDI;
            }

            throw new ArgumentException();
        }
        /// <summary>
        /// Returns a decoded register from an operand byte register code.
        /// </summary>
        /// <param name="regCode">Operand byte register code.</param>
        /// <returns>Decoded register from the operand byte register code.</returns>
        private static CodeOperand DecodeRb(int regCode)
        {
            switch(regCode)
            {
            case 0: return CodeRegister.AL;
            case 1: return CodeRegister.CL;
            case 2: return CodeRegister.DL;
            case 3: return CodeRegister.BL;
            case 4: return CodeRegister.AH;
            case 5: return CodeRegister.CH;
            case 6: return CodeRegister.DH;
            case 7: return CodeRegister.BH;
            }

            throw new ArgumentException();
        }
        /// <summary>
        /// Returns a decoded register from an operand segment register code.
        /// </summary>
        /// <param name="regCode">Operand segment register code.</param>
        /// <returns>Decoded register from the operand segment register code.</returns>
        private static CodeOperand DecodeSreg(int regCode)
        {
            switch(regCode)
            {
            case 0: return CodeRegister.ES;
            case 1: return CodeRegister.CS;
            case 2: return CodeRegister.SS;
            case 3: return CodeRegister.DS;
            case 4: return CodeRegister.FS;
            case 5: return CodeRegister.GS;
            }

            throw new ArgumentException();
        }
        private static CodeOperand DecodeRmbw(int mod, int rm, bool byteVersion, PrefixState prefixes, ref OperandReader reader)
        {
            if((prefixes & PrefixState.AddressSize) != 0)
                return DecodeRmbw32(mod, rm, byteVersion, prefixes, ref reader);
            else
                return DecodeRmbw16(mod, rm, byteVersion, prefixes, ref reader);
        }

        /// <summary>
        /// Returns a decoded register or memory byte or word from an operand.
        /// </summary>
        /// <param name="mod">Mod value from the instruction's ModR/M byte.</param>
        /// <param name="rm">R/M value from the instruction's ModR/M byte.</param>
        /// <param name="byteVersion">Value indicating whether the byte version of registers/pointers should be used.</param>
        /// <param name="prefixes">Prefixes in effect for the instruction.</param>
        /// <param name="reader">OperandReader instance for reader operand values.</param>
        /// <returns>Decodede register or memory byte or word.</returns>
        private static CodeOperand DecodeRmbw16(int mod, int rm, bool byteVersion, PrefixState prefixes, ref OperandReader reader)
        {
            CodeOperandSize operandSize = byteVersion ? CodeOperandSize.Byte : CodeOperandSize.Word;

            switch(mod)
            {
            case 0:
                if(rm == 6)
                    return new CodeOperand(CodeMemoryBase.DisplacementOnly, reader.ReadUInt16(), operandSize);
                else
                    return new CodeOperand(DecodeEffectiveAddress16(rm), 0, operandSize);

            case 1:
                return new CodeOperand(DecodeEffectiveAddress16(rm), (uint)(int)reader.ReadSByte(), operandSize);

            case 2:
                return new CodeOperand(DecodeEffectiveAddress16(rm), (uint)(int)reader.ReadInt16(), operandSize);

            case 3:
                if(byteVersion)
                    return DecodeRb(rm);
                else
                    return DecodeRw(rm, prefixes);
            }

            throw new ArgumentException();
        }
        private static CodeOperand DecodeRmbw32(int mod, int rm, bool byteVersion, PrefixState prefixes, ref OperandReader reader)
        {
            CodeOperandSize operandSize = GetOperandSize(byteVersion, prefixes);
            uint displacement = 0;
            CodeOperand operand;

            if(mod == 3)
            {
                if(byteVersion)
                    return DecodeRb(rm);
                else
                    return DecodeRw(rm, prefixes);
            }

            // SIB byte follows ModR/M if rm is 4.
            if(rm == 4)
            {
                operand = DecodeSib(mod, reader.ReadByte(), ref reader);
                operand.OperandSize = operandSize;
            }
            else
            {
                if(rm == 5 && mod == 0)
                    operand = new CodeOperand(CodeMemoryBase.DisplacementOnly, reader.ReadUInt32(), operandSize);
                else
                    operand = new CodeOperand(DecodeEffectiveAddress32(rm), 0, operandSize);
            }

            if(mod == 1)
                displacement = (uint)(int)reader.ReadSByte();
            else if(mod == 2)
                displacement = reader.ReadUInt32();

            operand.ImmediateValue += displacement;

            return operand;
        }

        private static CodeOperandSize GetOperandSize(bool byteVersion, PrefixState prefixes)
        {
            CodeOperandSize operandSize;
            if(byteVersion)
                operandSize = CodeOperandSize.Byte;
            else if((prefixes & PrefixState.OperandSize) != 0)
                operandSize = CodeOperandSize.DoubleWord;
            else
                operandSize = CodeOperandSize.Word;

            return operandSize;
        }

        /// <summary>
        /// Returns the base address for 16-bit addressing modes.
        /// </summary>
        /// <param name="rm">R/M value from the instruction's ModR/M byte.</param>
        /// <returns>Base address for 16-bit addressing.</returns>
        private static CodeMemoryBase DecodeEffectiveAddress16(int rm)
        {
            switch(rm)
            {
            case 0: return CodeMemoryBase.BX_plus_SI;
            case 1: return CodeMemoryBase.BX_plus_DI;
            case 2: return CodeMemoryBase.BP_plus_SI;
            case 3: return CodeMemoryBase.BP_plus_DI;
            case 4: return CodeMemoryBase.SI;
            case 5: return CodeMemoryBase.DI;
            case 6: return CodeMemoryBase.BP;
            case 7: return CodeMemoryBase.BX;
            }

            throw new ArgumentException();
        }

        private static CodeMemoryBase DecodeEffectiveAddress32(int rm)
        {
            switch(rm)
            {
            case 0: return CodeMemoryBase.EAX;
            case 1: return CodeMemoryBase.ECX;
            case 2: return CodeMemoryBase.EDX;
            case 3: return CodeMemoryBase.EBX;
            case 5: return CodeMemoryBase.EBP;
            case 6: return CodeMemoryBase.ESI;
            case 7: return CodeMemoryBase.EDI;
            }

            throw new ArgumentException();
        }

        private static CodeOperand DecodeSib(int mod, byte sib, ref OperandReader reader)
        {
            int scale = (sib >> 6) & 0x3;
            int index = (sib >> 3) & 0x7;
            int baseIndex = sib & 0x7;

            var operand = new CodeOperand(CodeMemoryBase.SIB, 0, CodeOperandSize.Word);
            operand.Scale = (byte)(1 << scale);
            operand.Index = DecodeIndexRegister(index);
            operand.Base = DecodeBaseRegister(baseIndex, mod);

            if(baseIndex == 5)
                operand.ImmediateValue = reader.ReadUInt32();

            return operand;
        }
        private static CodeSibRegister DecodeIndexRegister(int index)
        {
            switch(index)
            {
            case 0: return CodeSibRegister.EAX;
            case 1: return CodeSibRegister.ECX;
            case 2: return CodeSibRegister.EDX;
            case 3: return CodeSibRegister.EBX;
            case 4: return CodeSibRegister.None;
            case 5: return CodeSibRegister.EBP;
            case 6: return CodeSibRegister.ESI;
            case 7: return CodeSibRegister.EDI;

            default:
                throw new ArgumentException();
            }
        }
        private static CodeSibRegister DecodeBaseRegister(int baseIndex, int mod)
        {
            switch(baseIndex)
            {
            case 0: return CodeSibRegister.EAX;
            case 1: return CodeSibRegister.ECX;
            case 2: return CodeSibRegister.EDX;
            case 3: return CodeSibRegister.EBX;
            case 4: return CodeSibRegister.ESP;
            case 5: return mod == 0 ? CodeSibRegister.None : CodeSibRegister.EBP;
            case 6: return CodeSibRegister.ESI;
            case 7: return CodeSibRegister.EDI;

            default:
                throw new ArgumentException();
            }
        }
        #endregion
    }
}
